package mapspinner;

import mapspinner.util.ArrayUtils;
import mapspinner.util.GrassRenderPass;
import mapspinner.util.TextureRegistry;
import mapspinner.util.Vegetation.BatchingGrass;

import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.scene.Node;
import com.jme.scene.SharedMesh;
import com.jme.scene.TriMesh;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;

public class Grass extends Node {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4589440644711336071L;
	public static final int GRASS_LENGTH = 64;
	public static final float GRASS_SPACE = 5;
	public static final float GRASS_AREA = GRASS_LENGTH * GRASS_SPACE;
	private final Vector3f grassCenter = new Vector3f();
	private final Node grassRoot = new Node();
	private final SharedMesh[][] grassMeshes = new SharedMesh[GRASS_LENGTH][GRASS_LENGTH];
	private static long grassOffsetX = -GRASS_LENGTH / 2,
			grassOffsetZ = -GRASS_LENGTH / 2;
	private static BatchingGrass batchTool = new BatchingGrass();
	private static TriMesh grassMesh;
	private static Vector3f loadCenter;
	private static TextureState grassTextureState;

	private static TriMesh createGrassMesh() {
		if (grassMesh == null) {
			grassMesh = batchTool.createMesh(16f, 9, (short) 1);
			final Texture tex = TextureRegistry.getTexture("grassblades.png");
			grassTextureState = DisplaySystem.getDisplaySystem().getRenderer()
					.createTextureState();
			grassTextureState.setEnabled(true);
			grassTextureState.setTexture(tex);
			grassMesh.setRenderQueueMode(Renderer.QUEUE_SKIP);
			grassMesh.setRenderState(grassTextureState);
			grassMesh.setLocalTranslation(new Vector3f(-10f, -10f, -10f));
		}
		return grassMesh;
	}

	public static SharedMesh getNewSharedMesh() {
		if (grassMesh == null)
			createGrassMesh();
		final SharedMesh sm = new SharedMesh(grassMesh);
		final Quaternion rotQuat = new Quaternion();
		rotQuat.fromAngleAxis(FastMath.nextRandomFloat() * 90, new Vector3f(0,
				1, 0));
		sm.setLocalTranslation(new Vector3f(
				FastMath.nextRandomFloat() - 0.5f * 4, -2f, FastMath
						.nextRandomFloat() - 0.5f * 4));
		sm.setLocalRotation(rotQuat);
		sm.setLocalScale(0.7f);
		sm.getLocalScale().y = (FastMath.nextRandomFloat() * 0.5f + 0.5f) * 0.5f;
		return sm;
	}

	private final Terrain terrain;

	public Grass(final Vector3f newLoadCenter, final BasicPassManager pManager,
			final Terrain terrain) {
		final GrassRenderPass grp = new GrassRenderPass();
		grp.setShadingOnSpatial(grassRoot);
		grp.add(grassRoot);
		pManager.add(grp);
		createGrassNodes();
		attachChild(grassRoot);
		loadCenter = newLoadCenter;
		this.terrain = terrain;
	}

	public void createGrassNodes() {
		for (int x = 0; x < GRASS_LENGTH; x++)
			for (int z = 0; z < GRASS_LENGTH; z++) {
				final SharedMesh grassMesh = getNewSharedMesh();
				grassMeshes[x][z] = grassMesh;
				grassRoot.attachChild(grassMesh);
			}
	}

	@Override
	public void draw(final Renderer r) {
		for (int x = GRASS_LENGTH / 2 - 1; x >= 0; x--)
			new ArrayUtils.SpiralVisitor(GRASS_LENGTH, x, GRASS_LENGTH / 2 - x
					- 1) {
				@Override
				public void visit(final int x, final int z) {
					grassMeshes[x][z].onDraw(r);
				}
			};
	}

	public Vector3f getGrassCenter() {
		return grassCenter;
	}

	public void grassPosFromIndex(final int x, final int z, final Vector3f input) {
		input.set(GRASS_SPACE * (x + grassOffsetX), 0, GRASS_SPACE
				* (z + grassOffsetZ));
		input.y = terrain.getHeight(grassMeshes[x][z].getLocalTranslation());
	}

	public void shiftGrass(final int xShift, final int zShift) {
		grassOffsetX += xShift;
		grassOffsetZ += zShift;
		final Vector3f pos = new Vector3f();
		new ArrayUtils.Shifter(xShift, zShift, grassMeshes);
		grassCenter.set((grassOffsetX + GRASS_LENGTH / 2) * GRASS_SPACE, 0,
				(grassOffsetZ + GRASS_LENGTH / 2) * GRASS_SPACE);
		// TODO: Why does this only work if done twice?
		for (int x = 0; x < GRASS_LENGTH; x++)
			for (int z = 0; z < GRASS_LENGTH; z++) {
				final TriMesh grass = grassMeshes[x][z];
				grassPosFromIndex(x, z, pos);
				grass.getLocalTranslation().set(pos);
			}
	}

	public void update() {
		if (loadCenter.distance(getGrassCenter()) > GRASS_SPACE * 2) {
			final Vector3f grassCenter = getGrassCenter();
			shiftGrass((int) ((loadCenter.x - grassCenter.x) / GRASS_SPACE),
					(int) ((loadCenter.z - grassCenter.z) / GRASS_SPACE));
			grassCenter.y = loadCenter.y;
		}
	}

}
